package graphing;

import graphics.GraphingProperties;
import graphics.Graphable2D;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.geom.Rectangle2D;
import java.util.ArrayList;

/**
 *
 * Fully Tested: No
 * Fully Documented: No
 * Created: 11-Dec-2011
 * Last Updated: 11-Dec-2011
 *
 * @author Shimu Wu
 */
public class Graph {

    // ---------------- CONSTANTS -------------------------
    /** The color of the background */
    private final static Color BACKGROUND_COLOR = Color.WHITE;

    /** 
     * A Rectangle2D Object for erasing the entire graph (by drawing
     * a large rectangle that covers the entire screen with the above 
     * BACKGROUND COLOR) 
     */
    private final Rectangle2D background;

    private final Graphics2D graphics;

    /** The width of the screen */
    private final int screenWidth;

    /** The height of the screen */
    private final int screenHeight;

    private GraphGrid grid;

    /**
     * <p> Instantiates a Graph object that can be used to graph Functions onto
     * the given Graphics2D object. This constructor instantiates some
     * fields with the following values: </p>
     * <p> screenWidth: 640, screenHeight: 480 </p>
     * <p> xMin: -10, xMax: 10, xScale: 1 </p>
     * <p> yMin: -10, yMax: 10, yScale: 1 </p>
     * 
     * @param graphics the Graphics2D object to draw Functions onto
     */
    public Graph(Graphics2D graphics) {
        this(640, 480, graphics);
    }
    private final ArrayList<Tuple> graphables;

    /**
     * <p> Instantiates a Graph object that can be used to graph Functions onto
     * the given Graphics2D object. This constructor instantiates some
     * fields with the following values: </p>
     * <p> xMin: -10, xMax: 10, xScale: 1 </p>
     * <p> yMin: -10, yMax: 10, yScale: 1 </p>
     * 
     * @param screenWidth the width of the screen
     * @param screenHeight the height of the screen
     * @param graphics the Graphics2D object to draw Functions onto
     */
    public Graph(int screenWidth, int screenHeight,
            Graphics2D graphics) {
        this.screenWidth = screenWidth;
        this.screenHeight = screenHeight;
        this.graphics = graphics;

        background = new Rectangle2D.Double(0, 0, screenWidth, screenHeight);

        graphables = new ArrayList<Tuple>();

    }

    public boolean addGraphable(Graphable2D graphable) {
        return addGraphable(graphable, Color.BLACK, 2);
    }

    /**
     * Adds a Graphable2D Object to this Graph's list of Graphable2D Object.
     * The given Graphable2D Object will be drawn when this Graph's 
     * draw() method is called. 
     * 
     * @param graphable a Graphable2D Object to be drawn to the screen.
     * @param color the color to use when drawing the given Graphable2D Object
     * @param width the width of the line when drawing the given 
     * Graphable2D Object
     * 
     * @return true if the given Graphable2D Object was added successfully,
     * false if the given Graphable2D Object contains illegal 
     * GraphingProperties, for instance, one of its parameters is infinite, 
     * or the minimum value is greater than the maximum value
     */
    public boolean addGraphable(Graphable2D graphable, Color color, int width) {
        GraphingProperties properties = graphable.getGraphProperties();
        if (Double.isInfinite(properties.getxMin())
                || Double.isInfinite(properties.getxMax())
                || Double.isInfinite(properties.getyMin())
                || Double.isInfinite(properties.getyMax())
                || (properties.getxMin() > properties.getxMax())
                || (properties.getyMin() > properties.getyMax())) {
            return false;
        }
        graphables.add(new Tuple(graphable, color, width));
        return true;
    }

    /**
     * A class is simply used for holding Objects so only one Object needs
     * to be stored in an ArrayList
     */
    private class Tuple {

        private Graphable2D function;

        private Color color;

        private int width;

        private Tuple(Graphable2D function, Color color, int width) {
            this.function = function;
            this.color = color;
            this.width = width;
        }
    }

    /**
     * Calculates the xMin, xMax, xScale, yMin, yMax, yScale,
     * xMagnification, yMagnification. 
     */
    private GraphGrid calculateScale() {
        if (graphables.isEmpty()) {
            return new GraphGrid(screenWidth, screenHeight,
                    -10, 10, -10, 10);
        }

        /*
         * xMin, yMin will be set to the minimum xMin, yMin respectively 
         * found in the list of Graphable2D Objects. 
         * xMax, yMax will be set to the maximum xMax, yMax respectively 
         * found in the list of Graphable2D Objects. 
         */
        double xMin = Double.POSITIVE_INFINITY;
        double yMin = Double.POSITIVE_INFINITY;
        
        double xMax = Double.NEGATIVE_INFINITY;        
        double yMax = Double.NEGATIVE_INFINITY;

        GraphingProperties properties;
        for (Tuple tuple : graphables) {
            properties = tuple.function.getGraphProperties();
            xMin = Math.min(xMin, properties.getxMin());
            xMax = Math.max(xMax, properties.getxMax());
            
            yMin = Math.min(yMin, properties.getyMin());
            yMax = Math.max(yMax, properties.getyMax());
        }
        
        return new GraphGrid(screenWidth, screenHeight,
                xMin, xMax, yMin, yMax);
    }
    
    /** 
     * Draws the given equations 
     */
    public void draw() {
        graphics.setColor(BACKGROUND_COLOR);
        graphics.fill(background);

        grid = calculateScale();
        grid.drawGrid(graphics);

        for (Tuple tuple : graphables) {
            grid.drawEquation(graphics, 
                    tuple.function, tuple.color, tuple.width);
        }

    }
}